package cn.wolfcode.wolf2w.strategy.service.impl;

import cn.wolfcode.wolf2w.common.redis.service.RedisService;
import cn.wolfcode.wolf2w.common.redis.util.RedisKeys;
import cn.wolfcode.wolf2w.common.security.utils.SecurityUtils;
import cn.wolfcode.wolf2w.strategy.api.domain.*;
import cn.wolfcode.wolf2w.strategy.listener.event.StrategyCRUDEvent;
import cn.wolfcode.wolf2w.strategy.mapper.StrategyContentMapper;
import cn.wolfcode.wolf2w.strategy.mapper.StrategyMapper;
import cn.wolfcode.wolf2w.strategy.query.StrategyQuery;
import cn.wolfcode.wolf2w.strategy.service.IStrategyCatalogService;
import cn.wolfcode.wolf2w.strategy.service.IStrategyService;
import cn.wolfcode.wolf2w.strategy.service.IStrategyThemeService;
import cn.wolfcode.wolf2w.strategy.util.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 攻略Service业务层处理
 * 
 * @author dafei
 * @date 2023-11-22
 */
@Service
@Transactional
public class StrategyServiceImpl extends ServiceImpl<StrategyMapper,Strategy> implements IStrategyService {
    @Autowired
    private IStrategyCatalogService strategyCatalogService;
    @Autowired
    private IStrategyThemeService strategyThemeService;

    @Autowired
    private StrategyContentMapper strategyContentMapper;

    @Autowired
    private RedisService redisService;

    @Override
    public IPage<Strategy>   queryPage(StrategyQuery qo) {
        IPage<Strategy> page = new Page<>(qo.getCurrentPage(), qo.getPageSize());
        LambdaQueryChainWrapper<Strategy> wrapper = lambdaQuery();
        Long refid = qo.getRefid();
        Integer type = qo.getType();

        if(refid !=null && type != null){
            if(type == StrategyCondition.TYPE_THEME){
                wrapper.eq(Strategy::getThemeId, refid);
            }else if(type == StrategyCondition.TYPE_ABROAD || type == StrategyCondition.TYPE_CHINA){
                wrapper.eq(Strategy::getDestId, refid);
            }
        }
        return wrapper.page(page);
    }


    @Autowired
    private ApplicationContext ctx;


    @Override
    public boolean saveOrUpdate(Strategy entity) {
        //目的地id/name
        StrategyCatalog catalog = strategyCatalogService.getById(entity.getCatalogId());
        entity.setDestId(catalog.getDestId());
        entity.setDestName(catalog.getDestName());
        //攻略分类name
        entity.setCatalogName(catalog.getName());
        //攻略主题name
        StrategyTheme theme = strategyThemeService.getById(entity.getThemeId());
        entity.setThemeName(theme.getName());
        boolean flag = true;
        if(entity.getId() == null){
            //创建时间
            entity.setCreateTime(new Date());
            //各种统计数
            entity.setViewnum(0);
            entity.setReplynum(0);
            entity.setThumbsupnum(0);
            entity.setSharenum(0);
            entity.setFavornum(0);

            flag = super.save(entity);
            //内容
            StrategyContent content = entity.getContent();
            content.setId(entity.getId());
            strategyContentMapper.insert(content);
        }else{

            flag = super.updateById(entity);
            //内容
            StrategyContent content = entity.getContent();
            content.setId(entity.getId());
            strategyContentMapper.updateById(content);
        }

        //方案1：同时修改
        //mysql 做完， es马上操作
        //缺点： 1>同步操作（1:增加了响应时间 2：存在异常一起事务回滚问题）   2>违背接口设计原则： 单一职责 原则
        //strategyEsService.save(entityEs);
        //方案2：异步修改--方案1：优化
        //缺点：1>违背接口设计原则： 单一职责 原则 2>代码不优雅
        /*new Thread(()->{
            try{
                //strategyEsService.save(entityEs);
            }catch(Exception e){
            }
        }).start();*/
        //方案3：自定义spring事件 + 监听器
        //缺点： 1>默认情况是同步---该使用异步事件监听   2>es同步失败之后，无法重试
        // 1>自定义一个攻略crud事件--类比js-点击事件--StrategyCRUDEvent
        // 2>自定义一个攻略crud事件监听器--绑定es crud操作逻辑---类比js--响应函数--StrategyCRUDListener
        // 3>当mysql 中攻略发送DML操作， 触发攻略crud事件， 荣国监听器绑定逻辑，同步/异步执行es操作---类比js中按钮点击
        //ctx.publishEvent(new StrategyCRUDEvent("对象json", 1/2/3));

        //方案4：使用消息中间件(各类MQ--message queue)
        //缺点：数据加载(mysql数据变动)需代码控制
        //1>当mysql 中攻略发送DML操作， 将这些这个操作封装成消息（"对象json", 1/2/3） 【数据 + 操作】, 推送到MQ中
        //2>es微服务，实时监听MQ， 一旦返现有消息， 拉取消息，并消费消息(解下数据+ 操作， 执行对应同步逻辑)。

        //方案5：使用消息中间件 + 数据同步组件：canal
        //1>让canal 伪装成mysql的从库，监控mysql主库推送同步数据， 然后直接将变动数据封装成消息对象，放置到MQ中

        return flag;
    }

    @Override
    public StrategyContent getContent(Long id) {
        return strategyContentMapper.selectById(id);
    }

    @Override
    public List<Strategy> queryByCatalogId(Long cid) {
        return lambdaQuery().eq(Strategy::getCatalogId, cid).list();
    }

    @Override
    public List<Strategy> queryVeiwnumTop3(Long destId) {

        return lambdaQuery()
                .eq(Strategy::getDestId, destId)
                .orderByDesc(Strategy::getViewnum)
                .last(" limit 3")
                .list();
    }



    public String initStrategyHash(Long sid){
        String key = RedisKeys.STRATEGY_HASH.join(sid.toString());
        //判断key是否存在
        if(!redisService.hasKey(key)){
            //不存在， 创建，初始化， 缓存
            Strategy strategy = super.getById(sid);
            Map<String, Object> map = new HashMap<>();

            map.put("viewnum", strategy.getViewnum());
            map.put("replynum", strategy.getReplynum());
            map.put("favornum", strategy.getFavornum());
            map.put("sharenum", strategy.getSharenum());
            map.put("thumbsupnum", strategy.getThumbsupnum());
            map.put("strategyId", strategy.getId());
            //map.put("strategyId2", strategy.getId().intValue());
            redisService.setCacheMap(key, map);
        }
        return key;
    }
    private  Map<String, Object> numIncr(Long sid, String field, Long num){
        //拼接ahsh key
        String key = this.initStrategyHash(sid);
        //viewnum + 1
        redisService.incrementCacheMapValue(key, field, num);
        //返回hash
        return redisService.getCacheMap(key);
    }
    @Override
    public Map<String, Object> viewnumIncr(Long sid) {
        return this.numIncr(sid, "viewnum",1L);
    }
    @Override
    public Map<String, Object> replynumIncr(Long sid) {
        return this.numIncr(sid, "replynum", 1L);
    }

    @Override
    public Map<String, Object> favor(Long sid) {
        //解析出用户uid
        Long uid = SecurityUtils.getUserId();
        //使用uid拼接用户攻略收藏列表 key
        String key = RedisKeys.USER_FAVOR.join(uid.toString());
        //判断key 是否存在
        if(!redisService.hasKey(key)){
            //如果不存在， 创建， 初始化， 缓存
            //此处使用set---list
            Set<Long> set = new HashSet<>();   //查询 mysql 用户与攻略收藏类表  @TODO
            set.add(-1L);  //redis中如果set/list集合元素个数为0时， redis不开辟空间，也就是谁该key不存在
            redisService.setCacheSet(key, set);
        }
        boolean result = false;

/*
        if(redisService.redisTemplate.opsForList().indexOf(key, sid) >= 0){

        }*/


        //判断用户攻略收藏列表是否包含sid
        if(redisService.isCacheSetContains(key, sid)){
            //如果包含， 取消收藏操作， 收藏数-1 将sid 移除出用户攻略列表
            this.numIncr(sid, "favornum", -1L);
            redisService.deleteCacheSetValue(key, sid);
        }else{
            //如果不包含， 收藏操作， 收藏数+1 将sid 添加到用户攻略列表中
            this.numIncr(sid, "favornum", 1L);
            redisService.addCacheSetValue(key, sid);
            result = true;
        }
        //返回hash， 回显--操作result
        String hkey = RedisKeys.STRATEGY_HASH.join(sid.toString());
        Map<String, Object> hash = redisService.getCacheMap(hkey);
        hash.put("result", result);

        return hash;
    }

    @Override
    public Boolean isUserFavor(Long uid, Long sid) {
        String key = RedisKeys.USER_FAVOR.join(uid.toString());
        return redisService.isCacheSetContains(key, sid);
    }

    @Override
    public Map<String, Object> thumbsup(Long sid) {
        //用户id
        Long uid = SecurityUtils.getUserId();
        //拼接标记key
        String key = RedisKeys.STRATEGY_THUMB.join(uid.toString(), sid.toString());
        boolean flag = false;
        //判断标记key是否存在
        if(!redisService.hasKey(key)){
            //不存在， 点赞数 + 1， 缓存标记key， 有效时间当前时间到今天最后1秒
            Date now = new Date();
            Date end = DateUtil.getEndDate(now);
            Long time = DateUtil.getDateBetween(now, end);
            redisService.setCacheObject(key, "1", time, TimeUnit.SECONDS);
            this.numIncr(sid, "thumbsupnum", 1L);
            flag = true;
        }
        //查询hash ，并返回
        String hkey = RedisKeys.STRATEGY_HASH.join(sid.toString());
        Map<String, Object> hash = redisService.getCacheMap(hkey);
        hash.put("result", flag);
        return hash;
    }

    @Override
    public void redisDataPersistenceHandle() {
        //1：从redis 查出所有hash数据
        //  keys strategy_hash:*---scan
        String pattern = RedisKeys.STRATEGY_HASH.join("*");
        Collection<String> keys = redisService.keys(pattern);
        if(keys != null && keys.size() > 0){
            for (String key : keys) {
                Map<String, Object> hash = redisService.getCacheMap(key);

                System.out.println(hash);
                //2：将这些hash持久化到mysql-strategy中 sql = id = 1
                lambdaUpdate()
                        .eq(Strategy::getId, hash.get("strategyId"))
                        .set(Strategy::getViewnum, hash.get("viewnum"))
                        .set(Strategy::getReplynum, hash.get("replynum"))
                        .set(Strategy::getFavornum, hash.get("favornum"))
                        .set(Strategy::getSharenum, hash.get("sharenum"))
                        .set(Strategy::getThumbsupnum, hash.get("thumbsupnum"))
                        .update();

            }
        }

    }
}
